﻿
using Microsoft.IdentityModel.Tokens;
using SimpleTicketBooking.Config;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;

namespace SimpleTicketBooking.Service.Impl
{
    /// <summary>
    /// 
    /// </summary>
    public class SimpleJWT : IJWT
    {
        private readonly JwtConfig _jwtConfig = new JwtConfig();
        private byte[] signingKey;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="configration"></param>
        public SimpleJWT(IConfiguration configration)
        {
            configration.GetSection("Jwt").Bind(_jwtConfig);
            GenrateSigningKey();
        }

        /// <summary>
        /// generate signature
        /// 需要用到byte[64]位固定长度签名,所以这里用签名算法做是最为稳妥的.
        /// </summary>
        private void GenrateSigningKey()
        {
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
            byte[] keyByte = encoding.GetBytes(_jwtConfig.SecretKey);
            byte[] messageBytes = encoding.GetBytes(_jwtConfig.SecretKey);
            using (HMACSHA512 hmacsha512 = new HMACSHA512(keyByte))
            {
                signingKey = hmacsha512.ComputeHash(messageBytes);
            }
        }

        /// <summary>
        /// 生成Token
        /// </summary>
        /// <param name="Claims"></param>
        /// <returns></returns>
        public string GetToken(IDictionary<string, object> Claims)
        {
            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
            SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
            {
                Issuer = _jwtConfig.Issuer,
                Audience = _jwtConfig.Audience,
                NotBefore = DateTime.Now,
                Expires = DateTime.Now.AddMinutes(_jwtConfig.Lifetime),
                Claims = Claims,
                //不同的签名算法要求的key长度是不同的,根据算法为SymmetricSecurityKey传参
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(signingKey),
                                           SecurityAlgorithms.HmacSha512Signature)
            };
            SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(securityToken);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="Token"></param>
        /// <param name="Clims"></param>
        /// <returns></returns>
        public bool ValidateToken(string Token, out Dictionary<string, object> Clims)
        {
            Clims = new Dictionary<string, object>();
            ClaimsPrincipal? principal = null;
            if (string.IsNullOrWhiteSpace(Token))
            {
                return false;
            }

            JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
            try
            {
                JwtSecurityToken jwt = handler.ReadJwtToken(Token);
                if (jwt == null)
                {
                    return false;
                }

                TokenValidationParameters validationParameters = new TokenValidationParameters
                {
                    RequireExpirationTime = true,
                    ClockSkew = TimeSpan.Zero,
                    IssuerSigningKey = new SymmetricSecurityKey(signingKey),//签名验证算法
                    ValidateIssuer = true,//是否验证Issuer
                    ValidateAudience = true,//是否验证Audience
                    ValidateLifetime = _jwtConfig.ValidateLifetime,//是否验证失效时间
                    ValidAudience = _jwtConfig.Audience,
                    ValidIssuer = _jwtConfig.Issuer,
                    ValidateIssuerSigningKey = true,//是否验证签名
                };

                principal = handler.ValidateToken(Token, validationParameters, out SecurityToken securityToken);
                foreach (Claim item in principal.Claims)
                {
                    Clims.Add(item.Type, item.Value);
                }

                return true;
            }
            catch (SecurityTokenInvalidLifetimeException ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
        }
    }
}
